#include "wecom_pusher.h"
#include "qtch/log.h"
#include "qtch/config.h"
#include "qtch/http/http_connection.h"
#include <json/json.h>
#include "qtch/tinyxml2.h"

namespace qtch {
namespace infoPush {

qtch::Logger::ptr logger = QTCH_LOG_NAME("system");

static qtch::http::HttpConnectionPool::ptr WeCom_HttpConnectionPool 
    = qtch::http::HttpConnectionPool::Create("https://qyapi.weixin.qq.com","qyapi.weixin.qq.com",10,1000*30,5);

WeComPusher::WeComPusher(const std::string& corpid, const std::string& secret
        , const std::string& agentid){
    m_corpid = corpid;
    m_access_token = "";
    m_secret = secret;
    m_agentid = agentid;
    m_access_token_expire_time = 0;
}

const std::string WeComPusher::pushTypeToString(PushType type){
    switch(type){
        case NONE:
            return "none";
        case TEXT:
            return "text";
        case IMAGE:
            return "image";
        case VOICE:
            return "voice";
        case VIDEO:
            return "video";
        case FILE:
            return "file";
    }
    return "unknow";
}

bool WeComPusher::isAccessTokenExpire() {
    RWMutexType::ReadLock lock(m_mutex);
    return time(0) > m_access_token_expire_time;
}

const std::string& WeComPusher::getAccessToken(){
    RWMutexType::ReadLock lock(m_mutex);
    return m_access_token;
}

bool WeComPusher::reflashAccessToken(bool force) {
    if(!force && !isAccessTokenExpire()){
       return true; 
    }
    std::stringstream ss;
    ss << "https://qyapi.weixin.qq.com/cgi-bin/gettoken?";
    ss << "corpid=" << m_corpid;
    ss << "&corpsecret=" << m_secret;
    std::map<std::string, std::string> header;
    header["connection"] = "keep-alive";
    qtch::http::HttpResult::ptr http_result = WeCom_HttpConnectionPool->DoGet(ss.str(),2000,header);
    if(http_result->m_result != qtch::http::HttpResult::Error::OK){
        QTCH_LOG_ERROR(logger) << "reflashAccessToken http Error:" << (int)(http_result->m_result)
            << " ErrorStr:" << qtch::http::HttpResult::GetErrorString(http_result->m_result);
        m_errorCode = -2;
        m_errorMessage = qtch::http::HttpResult::GetErrorString(http_result->m_result);
        return false;
    }
    qtch::http::HttpResponse::ptr httpResponse = http_result->m_resonse;
    if(httpResponse->getStatus()!=200){
        QTCH_LOG_ERROR(logger) << "reflashAccessToken http status:" << httpResponse->getStatus()
            << " resp:\n" <<httpResponse;
        m_errorCode = -2;
        std::stringstream ss1;
        ss1 << "reflashAccessToken http status:" << httpResponse->getStatus();
        m_errorMessage = ss1.str();
        return false;
    }

    Json::Value jsonValue;
    Json::Reader jsonReader;
    std::stringstream ss1(httpResponse->getBody());
    if(!jsonReader.parse(ss1,jsonValue,true)){
        QTCH_LOG_ERROR(logger) << "reflashAccessToken json parse error";
        QTCH_LOG_DEBUG(logger) << "reflashAccessToken json parse error,response:" << httpResponse;
        m_errorCode = -2;
        m_errorMessage = "json parse error";
        return false; 
    }

    m_errorCode = jsonValue["errcode"].asInt();
    m_errorMessage = jsonValue["errmsg"].asString();
    if(m_errorCode){
        return false;
    }

    m_access_token = jsonValue["access_token"].asString();
    int expires_in = jsonValue["expires_in"].asInt();
    m_access_token_expire_time = time(0) + expires_in;
    return true;

}

bool WeComPusher::sendTextMessageToUser(const std::string& content,const std::string& send_user) {
    if(!reflashAccessToken()){
        return false;
    }
    Json::Value data;
    data["touser"] = send_user;
    data["msgtype"] = "text";
    data["agentid"] = m_agentid;
    data["text"]["content"] = content;
    std::stringstream ss_url;
    ss_url << "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" << m_access_token;
    
    return httpSend(qtch::http::HttpMethod::POST,ss_url.str(),data.toStyledString());
}

bool WeComPusher::UploadMedia(PushType type ,const std::string& data,std::string& media_id) {
    // 待实现
    return false;
}

bool WeComPusher::sendMediaMessage(PushType type ,const std::string& media_id,const std::string& send_user) {
    if(!reflashAccessToken()){
        return false;
    }
    Json::Value data;
    data["touser"] = send_user;
    std::string typeString = pushTypeToString(type);
    data["msgtype"] = typeString;
    data["agentid"] = m_agentid;
    data[typeString]["media_id"] = media_id;
    std::stringstream ss_url;
    ss_url << "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" << m_access_token;

    return httpSend(qtch::http::HttpMethod::POST,ss_url.str(),data.toStyledString());
}

bool WeComPusher::httpSend(qtch::http::HttpMethod method,const std::string& url,const std::string& data){
    int32_t retry = 2;
    while(retry--){
        std::map<std::string, std::string> header;
        header["Host"] = "qyapi.weixin.qq.com";
        header["connection"] = "keep-alive";
        qtch::http::HttpResult::ptr http_result = WeCom_HttpConnectionPool->DoRequest(method,url,5000,header,data);
        if(http_result->m_result != qtch::http::HttpResult::Error::OK){
            m_errorCode = -2;
            m_errorMessage = http_result->m_error;
            return false;
        }
        qtch::http::HttpResponse::ptr httpResponse = http_result->m_resonse;
        if(httpResponse->getStatus()!=200){
            QTCH_LOG_ERROR(logger) << "httpSend http status:" << httpResponse->getStatus();
            m_errorCode = -2;
            std::stringstream ss1;
            ss1 << "httpSend http status:" << httpResponse->getStatus();
            m_errorMessage = ss1.str();
            return false;
        }
        if(httpResponse->getStatus()!=200){
            QTCH_LOG_ERROR(logger) << "httpSend http status:" << httpResponse->getStatus();
            m_errorCode = -2;
            std::stringstream ss1;
            ss1 << "httpSend http status:" << httpResponse->getStatus();
            m_errorMessage = ss1.str();
            return false;
        }

        Json::Value jsonValue;
        Json::Reader jsonReader;
        std::stringstream ss(httpResponse->getBody());
        if(!jsonReader.parse(ss,jsonValue,true)){
            QTCH_LOG_ERROR(logger) << "httpSend json parse error";
            QTCH_LOG_DEBUG(logger) << "httpSend json parse error,response:" << httpResponse;
            m_errorCode = -2;
            m_errorMessage = "json parse error";
            return false; 
        }
        m_errorCode = jsonValue["errcode"].asInt();
        m_errorMessage = jsonValue["errmsg"].asString();
        if(m_errorCode == 0){
            return true;
        }
        if(m_errorCode == 42001){
            reflashAccessToken(true);
        }else if(m_errorCode == -1){
            sleep(1);
        }else{
            break;
        }
    }
    return false;
}

WXBizMsgCrypt::ptr WeComPusherManager::getCrypt(){
    if(m_corpid.empty()||m_token.empty()||m_encodingAESKey.empty()){
        return nullptr;
    }
    return std::make_shared<WXBizMsgCrypt>(m_token,m_encodingAESKey,m_corpid);
}

void WeComPusherManager::init(const std::string& corpid,const std::string& token,const std::string& encodeAESKey){
    m_corpid = corpid;
    m_token = token;
    m_encodingAESKey = encodeAESKey;
}

void WeComPusherManager::addWecom(const std::string& name,const std::string& corpid
        ,const std::string& secret,const std::string& agentid){
    WeComPusher::ptr wecom_ptr = nullptr;
    {
        RWMutexType::ReadLock lock(m_mutex);

        auto it = m_data.find(name);
        if(it != m_data.end()){
            wecom_ptr = it->second;
        }
    }
    if(wecom_ptr){
        wecom_ptr->setAgentid(agentid);
        wecom_ptr->setCorpid(corpid);
        wecom_ptr->setSecret(secret);

    }else{
        RWMutexType::WriteLock lock(m_mutex);
        m_data[name] = WeComPusher::ptr(new WeComPusher(corpid,secret,agentid));
    }

}

void WeComPusherManager::delWecom(const std::string& name){
    RWMutexType::WriteLock lock(m_mutex);
    m_data.erase(name);
}

WeComPusher::ptr WeComPusherManager::getWecom(const std::string& name){
    RWMutexType::ReadLock lock(m_mutex);
    auto it = m_data.find(name);
    if(it == m_data.end()){
        return nullptr;
    }
    return it->second;
}



WeComPusherBuild::WeComPusherBuild(std::string name){
    m_name = name;
    m_type = WeComPusher::PushType::NONE;
    m_errorCode = 0;
    m_isToAllUser = false;
    m_media_id = "";
}

WeComPusherBuild::ptr WeComPusherBuild::build(const std::string& name) {
    WeComPusherBuild::ptr pushBuildPtr = WeComPusherBuild::ptr(new WeComPusherBuild(name));
    return pushBuildPtr;
}

WeComPusherBuild::ptr WeComPusherBuild::addUser(const std::string& userid) {
    if(m_isToAllUser){
        QTCH_LOG_WARN(logger) << "WeComPusherBuild already to All user,can't addUser " << userid;
        return shared_from_this();
    }
    if(userid=="@all"){
        return toAllUser();
    }
    for(auto& it : m_send_users){
        if(it==userid){
            QTCH_LOG_WARN(logger) << "WeComPusherBuild add user " << userid << " is already exited";
            return shared_from_this();
        }
    }
    m_send_users.push_back(userid);
    return shared_from_this();
}

WeComPusherBuild::ptr WeComPusherBuild::toAllUser() {
    if(m_send_users.size()!=0){
        QTCH_LOG_WARN(logger) << "WeComPusherBuild already add user ,can't to All user";
    }
    m_isToAllUser = true;
    return shared_from_this();
    
}

WeComPusherBuild::ptr WeComPusherBuild::sendTextMessage(const std::string& text) {
    return sendType(WeComPusher::PushType::TEXT,text);
}

WeComPusherBuild::ptr WeComPusherBuild::sendVideoMessage(const std::string& video) {
    return sendType(WeComPusher::PushType::VIDEO,video);
}

WeComPusherBuild::ptr WeComPusherBuild::sendImageMessage(const std::string& image) {
    return sendType(WeComPusher::PushType::IMAGE,image);
}

WeComPusherBuild::ptr WeComPusherBuild::sendVoiceMessage(const std::string& voice) {
    return sendType(WeComPusher::PushType::VOICE,voice);
}

WeComPusherBuild::ptr WeComPusherBuild::sendFileMessage(const std::string& file) {
    return sendType(WeComPusher::PushType::FILE,file);
}

WeComPusherBuild::ptr WeComPusherBuild::sendType(WeComPusher::PushType type,const std::string& data) {
    if(m_type!=WeComPusher::PushType::NONE){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild type can't change";
        return shared_from_this();
    }
    if(type == WeComPusher::PushType::NONE){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild type can't be None";
        return shared_from_this();
    }
    if(data.size()==0){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild can't send null data";
        return shared_from_this();
    }
    switch(type){
        case WeComPusher::PushType::TEXT:
            if(data.length() > 2048){
                QTCH_LOG_WARN(logger) << "WeComPusherBuild send text data bigger than 2048, Exceeding will be truncated";
                return shared_from_this();
            }
            break;
        case WeComPusher::PushType::IMAGE:
            if(data.length() < 5 || data.length() > 2 * 1024 * 1024){
                QTCH_LOG_WARN(logger) << "WeComPusherBuild send image data can't less than 5B or bigger than 2MB";
                return shared_from_this();
            }
            break;
        case WeComPusher::PushType::VOICE:
            if(data.length() < 5 || data.length() > 2 * 1024 * 1024){
                QTCH_LOG_WARN(logger) << "WeComPusherBuild send voice data can't less than 5B or bigger than 2MB";
                return shared_from_this();
            }
            break;
        case WeComPusher::PushType::VIDEO:
            if(data.length() < 5 || data.length() > 10 * 1024 * 1024){
                QTCH_LOG_WARN(logger) << "WeComPusherBuild send video data can't less than 5B or bigger than 10MB";
                return shared_from_this();
            }
            break;
        case WeComPusher::PushType::FILE:
            if(data.length() < 5 || data.length() > 20 * 1024 * 1024){
                QTCH_LOG_WARN(logger) << "WeComPusherBuild send file data can't less than 5B or bigger than 20MB";
                return shared_from_this();
            }
            break;
        default:
            QTCH_LOG_ERROR(logger) << "WeComPusherBuild unknow type:" << type;
            return shared_from_this();
    }
    if(type!=WeComPusher::PushType::TEXT && !m_media_id.empty()){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild meida_id already existed, no need set media_data";
        return shared_from_this();
    }
    m_type = type;
    m_media_data = data;

    return shared_from_this();

}

WeComPusherBuild::ptr WeComPusherBuild::sendMediaId(WeComPusher::PushType type,const std::string& media_id){
    if(m_type!=WeComPusher::PushType::NONE){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild type can't change";
        return shared_from_this();
    }
    if(type == WeComPusher::PushType::NONE){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild type can't be None";
        return shared_from_this();
    }
    if(media_id.size()==0){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild can't set empty media_id";
        return shared_from_this();
    }

    switch(type){
        case WeComPusher::PushType::TEXT:
            QTCH_LOG_ERROR(logger) << "WeComPusherBuild media type can't be text";
            return shared_from_this();
        case WeComPusher::PushType::IMAGE:
        case WeComPusher::PushType::VOICE:
        case WeComPusher::PushType::VIDEO:
        case WeComPusher::PushType::FILE:
            break;
        default:
            QTCH_LOG_ERROR(logger) << "WeComPusherBuild unknow type:" << type;
            return shared_from_this();
    }
    m_type = type;
    m_media_id = media_id;
    return shared_from_this();
}

bool WeComPusherBuild::send() {
    WeComPusher::ptr wecomPush_ptr= WeComMgr::GetInstance()->getWecom(m_name);
    if(m_type == WeComPusher::PushType::NONE){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild type can't be none";
        return false;
    }
    std::string send_user = getSendUsers();
    if(send_user.empty()){
        QTCH_LOG_ERROR(logger) << "WeComPusherBuild type send user not add";
        return false;
    }
    if(m_type != WeComPusher::PushType::TEXT){ // 发送临时素材文件
        if(m_media_id.empty()){
            if(!wecomPush_ptr->UploadMedia(m_type,m_media_data,m_media_id)){
                QTCH_LOG_ERROR(logger) << "WeComPusher media upload faile, type:" << m_type
                        << "errorCode:" << wecomPush_ptr->getErrorCode()
                        << " errorMessage:" << wecomPush_ptr->getErrorMessage();
                m_errorCode = wecomPush_ptr->getErrorCode();
                m_errorMessage = wecomPush_ptr->getErrorMessage();
                return false;
            }
        }
        if(!wecomPush_ptr->sendMediaMessage(m_type, m_media_id,send_user)){
            m_errorCode = wecomPush_ptr->getErrorCode();
            m_errorMessage = wecomPush_ptr->getErrorMessage();
            return false;
        }
    }else{
        if(!wecomPush_ptr->sendTextMessageToUser(m_content,send_user)){
            m_errorCode = wecomPush_ptr->getErrorCode();
            m_errorMessage = wecomPush_ptr->getErrorMessage();
            return false;
        }
    }
    return true;
}

std::string WeComPusherBuild::getSendUsers(){
    std::stringstream ss;
    if(m_isToAllUser){
        ss << "@all";
    }else {
        if(m_send_users.size()==0){
            return "";
        }
        ss << m_send_users[0];
        for(size_t i = 1; i < m_send_users.size(); ++i){
            ss << "|" << m_send_users[i];
        }
    }
    return ss.str();
}


}
}